package com.lemon.cloud.oauth.web;

import cn.hutool.core.util.StrUtil;
import com.lemon.cloud.admin.entity.OAuthClientDetails;
import com.lemon.cloud.admin.feign.ClientDetailsFeign;
import com.lemon.cloud.comm.constants.OAuth2Constant;
import com.lemon.cloud.comm.model.ResultMsg;
import com.lemon.cloud.comm.model.RetOps;
import com.lemon.cloud.core.utils.SpringContextHolder;
import com.lemon.cloud.oauth.support.handler.AuthenticationFailureEventHandler;
import com.lemon.cloud.security.annotation.Inner;
import com.lemon.cloud.security.util.OAuth2EndpointUtils;
import com.lemon.cloud.security.util.OAuth2ErrorCodesExpand;
import com.lemon.cloud.security.util.OAuthClientException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.security.Principal;
import java.util.Map;
import java.util.Set;


/**
 * @author HuangDS
 */
@RestController
@RequestMapping("/oauth2")
@Slf4j
@RequiredArgsConstructor
public class OAuthTokenController {

    private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();

    private final AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureEventHandler();

    private final OAuth2AuthorizationService authorizationService;

    private final ClientDetailsFeign clientDetailsService;

    /**
     * 认证页面
     *
     * @param modelAndView
     * @param error        表单登录失败处理回调的错误信息
     * @return ModelAndView
     */
    @GetMapping("/login")
    public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
        modelAndView.setViewName("ftl/login");
        modelAndView.addObject("error", error);
        return modelAndView;
    }

    @RequestMapping("/confirm_access")
    public ModelAndView getAccessConfirmation(Principal principal,Map<String, Object> model, HttpServletRequest request)  {
        ModelAndView modelAndView = new ModelAndView();
        String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
        String state = request.getParameter(OAuth2ParameterNames.STATE);
        if (clientId == null) {
            modelAndView.setViewName("ftl/login");
            modelAndView.addObject("error", "clientId为空");
            return modelAndView;
        }
        Principal userPrincipal = request.getUserPrincipal();
        //这个用户已经退出登录，不能再授权
        OAuthClientDetails details = RetOps.of(clientDetailsService.getClientDetailsById(clientId))
                .getData()
                .orElseThrow(() -> new OAuthClientException("clientId 不合法"));
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            modelAndView.addObject(entry.getKey(), entry.getValue());
        }
        Set<String> authorizedScopes = StringUtils.commaDelimitedListToSet(details.getScope());
        modelAndView.addObject(OAuth2Constant.PRINCIPAL_NAME, userPrincipal.getName());
        modelAndView.addObject(OAuth2ParameterNames.CLIENT_ID, clientId);
        modelAndView.addObject(OAuth2ParameterNames.SCOPE, authorizedScopes);
        modelAndView.addObject(OAuth2ParameterNames.STATE,state);
        modelAndView.setViewName("ftl/confirm");
        return modelAndView;
    }

    @DeleteMapping("/logout")
    public ResultMsg<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
        if (StrUtil.isBlank(authHeader)) {
            return ResultMsg.resultSuccess();
        }

        String tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim();
        return removeToken(tokenValue);
    }

    /**
     * 令牌管理调用
     *
     * @param token token
     */
    @Inner
    @DeleteMapping("/{token}")
    public ResultMsg<Boolean> removeToken(@PathVariable("token") String token) {
        OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        if (authorization == null) {
            return ResultMsg.resultSuccess();
        }

        OAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();
        if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) {
            return ResultMsg.resultSuccess();
        }
        // 清空access token
        authorizationService.remove(authorization);
        // 处理自定义退出事件，保存相关日志
        SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(
                authorization.getPrincipalName(), authorization.getRegisteredClientId())));
        return ResultMsg.resultSuccess();
    }

    /**
     * 重写/oauth/check_token这个默认接口，用于校验令牌，返回的数据格式统一
     */
    @PostMapping(value = "/check_token")
    @SneakyThrows
    public void checkToken(@RequestParam("token") String token, HttpServletResponse response, HttpServletRequest request) {
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

        if (StrUtil.isBlank(token)) {
            httpResponse.setStatusCode(HttpStatus.FAILED_DEPENDENCY);
            this.authenticationFailureHandler.onAuthenticationFailure(request, response,
                    new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING));
            return;
        }
        OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);

        // 如果令牌不存在 返回401

        if (authorization == null || authorization.getAccessToken() == null) {
            httpResponse.setStatusCode(HttpStatus.FAILED_DEPENDENCY);
            this.authenticationFailureHandler.onAuthenticationFailure(request, response,
                    new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN));
            return;
        }

        Map<String, Object> claims = authorization.getAccessToken().getClaims();
        OAuth2AccessTokenResponse sendAccessTokenResponse = OAuth2EndpointUtils.sendAccessTokenResponse(authorization,
                claims);
        this.accessTokenHttpResponseConverter.write(sendAccessTokenResponse, MediaType.APPLICATION_JSON, httpResponse);
    }
}
